/*
 * Copyright 2008 Lucifer Lu
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;


using SilverChart.Configuration;
using SilverChart.Core.Log;


namespace SilverChart.Components {
    /// <summary>
    /// Description of YAxisComponent.
    /// </summary>
    public class YAxisComponent : CartesianCoordinateAxis {
        private static Logger logger = Logger.GetLogger<YAxisComponent>();

        private Double TickWidth;
        private Int32 LabelCount;
        private List<TextBlock> _Labels;

        private Path yAxisLine;
        private Path minorTickLines;
        private GeometryGroup minroTickGroup;
        private Path majorTickLines;
        private GeometryGroup majorTickGroup;

        public YAxisComponent(YAxis config)
            : base(config) {
            //Initial Value
            MaxValue = Double.NegativeInfinity;
            MinValue = Double.PositiveInfinity;
            _Labels = new List<TextBlock>();

            //Default Value
            LabelCount = 11;

            yAxisLine = AxisConfig.AxisLine;
            Children.Add(yAxisLine);

            minorTickLines = AxisConfig.MinorTickLine;
            minroTickGroup = new GeometryGroup();
            minorTickLines.Data = minroTickGroup;
            Children.Add(minorTickLines);

            majorTickLines = AxisConfig.MajorTickLine;
            majorTickGroup = new GeometryGroup();
            majorTickLines.Data = majorTickGroup;
            Children.Add(majorTickLines);
        }

        public override void InitializeWidgets() {
            ProcessElements();
            addLabels();
        }

        private void addLabels() {
            ValuePerTick = (MaxValue - MinValue) / (LabelCount - 1);
            Int32 labelIndex = 0;
            for (Double value = MinValue; Math.Round(value, 10) <= MaxValue; value = value + ValuePerTick) {
                TextBlock labelBlock = AxisConfig.LabelText;
                String labelText = "";
                if (labelIndex % AxisConfig.Step == 0) {
                    if (labelIndex < AxisConfig.Labels.Count) {
                        labelText = AxisConfig.Labels[labelIndex];
                    }
                    else {
                        labelText = Math.Round(value, 10).ToString();
                    }
                    labelBlock.Text = labelText;
                    _Labels.Add(labelBlock);
                    Children.Add(labelBlock);
                }
                logger.Debug(String.Format("Label[{0}]:{1}; Value:{2:F}", labelIndex, labelText, value));
                labelIndex++;
            }
            logger.Debug(String.Format("Label Count:{0}", labelIndex));
            logger.Debug(String.Format("Value Per Tick:{0}", ValuePerTick));
        }

        public override Double MappingValueToAxis(Double value) {
            //AxisHeight - TickWidth: the topest tick is empty space hold tick 
            return ActualHeight - (value - MinValue) / (MaxValue - MinValue) * (ActualHeight - TickWidth);
        }

        private Double MappingValueToAxis(Double value, Double height) {
            return height - (value - MinValue) / (MaxValue - MinValue) * (height - TickWidth);
        }

        private void ProcessElements() {
            if (AxisConfig.Labels.Count > 0) {
                LabelCount = AxisConfig.Labels.Count;
            }

            foreach (Element element in Datas) {
                foreach (Double dataValue in element.Values) {
                    logger.Debug("Value:" + dataValue);
                    if (Double.IsNaN((AxisConfig as YAxis).MaxValue)
                       && dataValue > MaxValue) {
                        logger.Debug("New Max Value[" + MaxValue + "]:" + dataValue);
                        MaxValue = dataValue;
                    }

                    if (Double.IsNaN((AxisConfig as YAxis).MinValue)
                       && dataValue < MinValue) {
                        MinValue = dataValue;
                    }
                }
            }
            if (!Double.IsNaN((AxisConfig as YAxis).MaxValue)) {
                MaxValue = (AxisConfig as YAxis).MaxValue;
            }

            if (!Double.IsNaN((AxisConfig as YAxis).MinValue)) {
                MinValue = (AxisConfig as YAxis).MinValue;
            }

            logger.Debug("Before Normalize:");
            logger.Debug(String.Format("Max:{0}", MaxValue));
            logger.Debug(String.Format("Min:{0}", MinValue));

            if (Double.IsNaN((AxisConfig as YAxis).MaxValue) ||
               Double.IsNaN((AxisConfig as YAxis).MinValue)) {
                NormalizeMaxMinValues();
            }
        }

        private void NormalizeMaxMinValues() {
            Double delta = (MaxValue - MinValue) / (LabelCount - 1);
            Double comp = Math.Round(Math.Log10(delta));
            Double exp = Math.Pow(10, comp);
            MaxValue = Math.Ceiling(MaxValue / exp) * exp;
            MinValue = Math.Floor(MinValue / exp) * exp;

            logger.Debug("After Normalize:");
            logger.Debug(String.Format("Max:{0}", MaxValue));
            logger.Debug(String.Format("Min:{0}", MinValue));
        }

        protected override Size MeasureOverride(Size availableSize) {
            Double width = AxisConfig.Thickness;
            width += Math.Max(AxisConfig.MinorTickHeight, AxisConfig.MajorTickHeight);

            TickWidth = availableSize.Height / LabelCount;

            Double maxLabelWidth = 0;
            foreach (TextBlock tb in _Labels) {
                tb.Measure(availableSize);
                if (tb.DesiredSize.Width > maxLabelWidth) {
                    maxLabelWidth = tb.DesiredSize.Width;
                }
            }

            width += maxLabelWidth;

            return new Size(width, availableSize.Height);
        }

        protected override Size ArrangeOverride(Size finalSize) {
            Double yAxisX = finalSize.Width - AxisConfig.Thickness / 2;

            LineGeometry axisLg = new LineGeometry();
            axisLg.StartPoint = new Point(yAxisX, finalSize.Height);
            axisLg.EndPoint = new Point(yAxisX, 0);
            yAxisLine.Data = axisLg;

            Double axisLineLeft = yAxisX - AxisConfig.Thickness / 2;
            Int32 tickCount = 0;
            Int32 labelIndex = 0;
            for (Double value = MinValue; Math.Round(value, 10) <= MaxValue; value = value + ValuePerTick) {
                Double tickTop = MappingValueToAxis(value, finalSize.Height);

                if (MajorTick(tickCount)) {
                    LineGeometry tickLg = new LineGeometry();
                    tickLg.StartPoint = new Point(axisLineLeft, tickTop);
                    tickLg.EndPoint = new Point(axisLineLeft - AxisConfig.MajorTickHeight, tickTop);
                    majorTickGroup.Children.Add(tickLg);

                    TextBlock tickLabel = _Labels[labelIndex];
                    Double labelLeft = axisLineLeft - tickLabel.DesiredSize.Width -
                        Math.Max(AxisConfig.MinorTickHeight, AxisConfig.MajorTickHeight);
                    Rect labelOutBorder = new Rect(labelLeft,
                        tickTop - tickLabel.DesiredSize.Height / 2,
                        tickLabel.DesiredSize.Width, tickLabel.DesiredSize.Height);
                    tickLabel.Arrange(labelOutBorder);
                    labelIndex++;
                }
                else {
                    LineGeometry tickLg = new LineGeometry();
                    tickLg.StartPoint = new Point(axisLineLeft, tickTop);
                    tickLg.EndPoint = new Point(axisLineLeft - AxisConfig.MinorTickHeight, tickTop);
                    minroTickGroup.Children.Add(tickLg);
                }
                tickCount++;
            }
            return finalSize;
        }

        public override string WidgetName {
            get {
                return "Y Axis Component";
            }
        }

        public override DockType Dock {
            get {
                return DockType.Left;
            }
        }

        public override bool MajorTick(int tickCount) {
            return tickCount % AxisConfig.Step == 0;
        }
    }
}
